package satellite

import (
	"fmt"
	"sync"
	"time"

	"gddgame.cc/galaxy/utils/def"
	"github.com/pkg/errors"
)

type Socket struct {
	def.Logger

	schemaMap  sync.Map
	transports TransportSlice

	dialed       bool
	dialedHandle func(conn Dial)
	dialedMap    sync.Map

	listened     bool
	listenHandle func(accept <-chan Conn)

	reconntime time.Duration
	reconnmax  time.Duration

	close  chan struct{}
	closed bool
}

func NewSocket() *Socket {
	s := &Socket{
		schemaMap:  sync.Map{},
		transports: []Transport{},
		close:      make(chan struct{}, 1),
		closed:     false,

		reconntime: time.Second,
		reconnmax:  time.Second * 30,
	}
	return s
}

func (socket *Socket) SetLogger(logger def.Logger) {
	socket.Logger = logger
	for k, _ := range socket.transports {
		transport := socket.transports[k]
		transport.SetLogger(socket.LoggerWrap("Transport", transport.Type()))
	}
}

func (socket *Socket) AddTransport(transport Transport) error {
	schema := transport.Schema()
	if _, ok := socket.schemaMap.Load(schema); ok {
		return errors.New("transport is exist:" + schema)
	}
	socket.schemaMap.Store(schema, true)
	if socket.Logger != nil {
		transport.SetLogger(socket.LoggerWrap("Transport", transport.Type()))
	}
	if socket.dialed {
		if err := socket.dial(transport); err != nil {
			return err
		}
	}
	if socket.listened {
		if err := socket.listen(transport); err != nil {
			return err
		}
	}
	socket.transports = socket.transports.Append(transport)
	return nil
}

func (socket *Socket) Listen(handle func(accept <-chan Conn)) error {
	if socket.listened {
		return nil
	}
	socket.listenHandle = handle
	if err := socket.listen(socket.transports...); err != nil {
		return err
	}
	socket.listened = true
	return nil
}

func (socket *Socket) listen(transports ...Transport) error {
	for k, _ := range transports {
		transport := transports[k]
		accept, err := transport.Listen()
		if err != nil {
			return err
		}
		socket.listenHandle(accept)
	}
	return nil
}

func (socket *Socket) Dial(handle func(conn Dial)) error {
	if socket.dialed {
		return nil
	}
	socket.dialedHandle = handle
	if err := socket.dial(socket.transports...); err != nil {
		return err
	}
	socket.dialed = true
	return nil
}

func (socket *Socket) dial(transports ...Transport) error {
	for k, _ := range transports {
		transport := transports[k]
		dial := newDial(socket, transport)
		if !dial.Connected() {
			return errors.New("transport dial fail:" + transport.Schema())
		}
		socket.dialedMap.Store(transport.Type(), dial)
		socket.dialedHandle(dial)
	}
	return nil
}

func (socket *Socket) Close() error {
	if socket.closed {
		return nil
	}
	socket.closed = true
	close(socket.close)
	closeHandle := func(transport Transport) {
		if transport != nil && transport.Connected() {
			if err := transport.Close(); err != nil {
				fmt.Println("socket close:", err)
			}
		}
	}
	for k, _ := range socket.transports {
		closeHandle(socket.transports[k])
	}
	return nil
}

type dial struct {
	Conn

	times int

	hook    DialHook
	err     chan struct{}
	success chan struct{}

	close  chan struct{}
	closed bool
}

func newDial(socket *Socket, transport Transport) *dial {
	rtime := socket.reconntime
	rtmax := socket.reconnmax
	dial := &dial{
		err:    make(chan struct{}, 1),
		close:  make(chan struct{}, 1),
		closed: false,
		times:  0,
	}
	var rt *time.Timer
	dial.success = make(chan struct{}, 1)
	go func() {
		for {
			conn, err := transport.Dial()
			if err == nil {
				// reset retry time
				rtime = socket.reconntime
				if socket.closed || dial.closed {
					_ = conn.Close()
					return
				}
				dial.Conn = conn
				dial.times = 0
				if dial.hook != nil {
					dial.hook.OnConnect(dial)
				}
				//dial.success <- struct{}{}
				close(dial.success)
				dial.success = make(chan struct{}, 1)
				select {
				case <-socket.close: // parent socket closed
				case <-dial.close: // dialer closed
				case <-dial.err: // dialer err
					//fmt.Println("wait reconnect socket", conn.Transport().Schema(), time.Now())
				}

				_ = conn.Close()
				dial.Conn = nil
				if dial.hook != nil {
					dial.hook.OnDisconnect(dial)
				}
			} else {
				dial.times += 1
				//fmt.Println("reconnect fail", err, dial.times, transport.Schema(), time.Now())
			}
			if socket.closed || dial.closed {
				goto Stop
			}
			// we're redialing here
			rt = time.NewTimer(rtime)
			select {
			case <-dial.close: // dialer closed
			case <-socket.close: // exit if parent socket closed
			case <-rt.C:
				//fmt.Println("reconnect", transport.Schema(), time.Now(), dial.times, rtime)
				if rtmax > 0 {
					rtime *= 2
					if rtime > rtmax {
						rtime = rtmax
					}
				}
				continue
			}
		Stop:
			if rt != nil {
				rt.Stop()
			}
			close(dial.success)
			return
		}
	}()
	<-dial.success
	return dial
}

func (dial *dial) Reconnect() <-chan struct{} {
	return dial.success
}

func (dial *dial) Close() error {
	if dial.closed {
		return nil
	}
	dial.closed = true
	close(dial.close)
	return nil
}

func (dial *dial) Opened() bool {
	// 自身连接状态，内部会自动重连
	return !dial.closed
}

//返回当前链接状态
func (dial *dial) Connected() bool {
	return !dial.closed && dial.Conn != nil
}

func (dial *dial) Error(err error) {
	if dial.closed {
		return
	}
	if dial.Conn != nil {
		dial.err <- struct{}{}
	}
	if dial.hook != nil {
		dial.hook.OnError(dial, err)
	}
}

func (dial *dial) Hook(hook DialHook) {
	dial.hook = hook
}
